﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Xml.Serialization;
using System.Linq;
using Kotonoha.Server.Utils;
using Kotonoha.Server.Extensions;
using log4net;


namespace Kotonoha.Server.Settings {
    public class SettingsManager {
        private readonly ILog log = LogManager.GetLogger(typeof (SettingsManager));
        private static readonly SettingsManager instance = new SettingsManager();
        internal static readonly Mutex settingsMutex = new Mutex(false, "Kotonoha.SettingsMutex");
        private readonly XmlSerializer serializer = new XmlSerializer(typeof(Setting));
        private readonly Dictionary<string, Setting> cache = new Dictionary<string, Setting>();
        private Timer timer;

        public static SettingsManager Instance {
            get {
                return instance;
            }
        }

        private SettingsManager() {
            this.timer = new Timer(CacheClear, this, TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(5));
//            Thread t = new Thread(CacheClear);
//            t.Name = "Settings flusher";
//            t.IsBackground = true;
//            t.Priority = ThreadPriority.BelowNormal;
//            t.Start();
        }

        private void CacheClear(object o) {
            try {
                settingsMutex.WaitOne();
                flushSettings();
            } finally {
                settingsMutex.ReleaseMutex();
            }
        }

        private void flushSettings() {
            cache.Values.
                Where(s => s.LifeEnd < DateTime.Now).
                Where(s => !s.clean).
                ForEach(save);
        }

        private void clearSettings() {
            List<string> toRemove = cache.
                Where(pair => pair.Value.clean).
                Where(p => p.Value.LifeEnd < DateTime.Now).
                Select(pair => pair.Key).
                ToList();
            toRemove.ForEach(s => cache.Remove(s));
        }

        private Setting Get(string name) {
            try {
                settingsMutex.WaitOne();
                Setting val;
                if (cache.TryGetValue(name, out val)) {
                    val.lastModDate = DateTime.Now;
                    return val;
                }
                string filename = formatPath(name);
                FileInfo file = new FileInfo(filename);
                
                val = file.Exists ? load(file) : new Setting();
                val.path = file.FullName;
                val.lastModDate = DateTime.Now;
                cache.Add(name, val);
                return val;
            } finally {
                settingsMutex.ReleaseMutex();
            }
        }

        private static string formatPath(string name) {
            return PathUtil.GetSettingsPath(name + ".xml");
        }

        public static Setting Acquire(string name) {
            return Instance.Get(name);
        }

        private Setting load(FileInfo file) {
            using (Stream s = file.OpenRead()) {
                Setting loaded;
                try {
                    loaded = serializer.Deserialize(s) as Setting;
                } catch (Exception e) {
                    log.Info("Error with deserialization", e);
                    return new Setting();
                }
                if (loaded == null) {
                    return new Setting();
                }
                return loaded;
            }
        }
        
        public void save(Setting setn) {
            if (!setn.clean) {
                FileStream write = File.Open(setn.path, FileMode.Create, FileAccess.Write, FileShare.None);
                serializer.Serialize(write, setn);
                setn.clean = true;
                write.Close();
                log.DebugFormat("Setting {0} was saved", Path.GetFileNameWithoutExtension(setn.path));
            }
        }

        /// <summary>
        /// Should be called on exit
        /// </summary>
        public void saveAll() {
            try {
                timer.Dispose();
                settingsMutex.WaitOne();
                cache.Values
                    .Where(v => !v.clean)
                    .ForEach(save);
            } finally {
                settingsMutex.ReleaseMutex();
            }
        }
    }
}
